/**
* @file keyboard.c
* Basic keyboard driver, handles operation with keyboard encoder and
* controller. Handles read scancodes and ascii conversion.
*
* Copyrights 2011 Michal Saman, m.saman@designplus.cz.
* This source code is release under the Apache License 2.0.
* http://www.apache.org/licenses/LICENSE-2.0
*/
#include <stdarg.h>
#include <stdint.h>
#include <ctype.h>
#include <drivers/kbrd/keyboard.h>
#include <hal/hal.h>
#include <main/kprintf.h>

/** Holds current scancode */
static char _scancode;

/** Holds status of locks keys */
static bool _numlock, _scrolllock, _capslock;

/** Holds status of special keys */
static bool _shift, _alt, _ctrl;

/** flag keyboard error */
static int _kbrd_error = 0;

/** flag set if Basic Assurance Test (BAT) failed */
static bool _kbrd_bat_res = false;

/** flag set if keyboard diagnostic failed */
static bool _kbrd_diag_res = false;

/** flag set if system should resend last command */
static bool _kbrd_resend_res = false;

/** set if keyboard is disabled */
static bool _kbrd_disable = false;

/** XT scancode table */
static int _kbrd_scancode_std [] = {
		// key				// scancode
		KEY_UNKNOWN,		// 0
		KEY_ESCAPE,			// 1
		KEY_1,				// 2
		KEY_2,				// 3
		KEY_3,				// 4
		KEY_4,				// 5
		KEY_5,				// 6
		KEY_6,				// 7
		KEY_7,				// 8
		KEY_8,				// 9
		KEY_9,				// 0xA
		KEY_0,				// 0xB

		KEY_MINUS,			// 0xC
		KEY_EQUAL,			// 0xD
		KEY_BACKSPACE,		// 0xE
		KEY_TAB,			// 0xF
		KEY_Q,				// 0x10
		KEY_W,				// 0x11
		KEY_E,				// 0x12
		KEY_R,				// 0x13
		KEY_T,				// 0x14
		KEY_Y,				// 0x15
		KEY_U,				// 0x16
		KEY_I,				// 0x17
		KEY_O,				// 0x18
		KEY_P,				// 0x19
		KEY_LEFTBRACKET,	// 0x1A
		KEY_RIGHTBRACKET,	// 0x1B
		KEY_RETURN,			// 0x1C
		KEY_LCTRL,			// 0x1D
		KEY_A,				// 0x1E
		KEY_S,				// 0x1F
		KEY_D,				// 0x20
		KEY_F,				// 0x21
		KEY_G,				// 0x22
		KEY_H,				// 0x23
		KEY_J,				// 0x24
		KEY_K,				// 0x25
		KEY_L,				// 0x26
		KEY_SEMICOLON,		// 0x27
		KEY_QUOTE,			// 0x28
		KEY_GRAVE, 			// 0x29
		KEY_LSHIFT,			// 0x2A
		KEY_BACKSLASH,		// 0x2B
		KEY_Z,				// 0x2C
		KEY_X,				// 0x2D
		KEY_C,				// 0x2E
		KEY_V,				// 0x2F
		KEY_B,				// 0x30
		KEY_N,				// 0x31
		KEY_M,				// 0x32
		KEY_COMMA,			// 0x33
		KEY_DOT,			// 0x34
		KEY_SLASH,			// 0x35
		KEY_RSHIFT,			// 0x36
		KEY_KP_ASTERISK,	// 0x37
		KEY_RALT,			// 0x38
		KEY_SPACE,			// 0x39
		KEY_CAPSLOCK,		// 0x3A
		KEY_F1,				// 0x3B
		KEY_F2,				// 0x3C
		KEY_F3,				// 0x3D
		KEY_F4,				// 0x3E
		KEY_F5,				// 0x3F
		KEY_F6,				// 0x40
		KEY_F7,				// 0x41
		KEY_F8,				// 0x42
		KEY_F9,				// 0x43
		KEY_F10,			// 0x44
		KEY_KP_NUMLOCK,		// 0x45
		KEY_SCROLLLOCK,		// 0x46
		KEY_HOME,			// 0x47
		KEY_KP_8,			// 0x48
		KEY_PAGEUP,			// 0x49
		KEY_KP_2,			// 0x50
		KEY_KP_3,			// 0x51
		KEY_KP_0,			// 0x52
		KEY_KP_DECIMAL,		// 0x53
		KEY_UNKNOWN,		// 0x54
		KEY_UNKNOWN,		// 0x55
		KEY_UNKNOWN,		// 0x56
		KEY_F11,			// 0x57
		KEY_F12				// 0x58
};

/** Constant representing invalid scancode. */
const int INVALID_SCANCODE = 0;


/** Function reads status from keyboard controller. */
uint8_t kbrd_ctrl_read_status() {
	return inportb(KBRD_CTRL_STATS_REG);
}

/**
 * Send commmand to keyboard controller.
 * Function checks if input buffer of controller is ready for input.
 * @param cmd	Command for keyboard controller
 */
void kbrd_ctrl_send_cmd(uint8_t cmd) {
	while(1)
		if ( (kbrd_ctrl_read_status() & KBRD_CTRL_STATS_MASK_IN_BUF) == 0 )
			break;
	outportb(KBRD_CTRL_CMD_REG, cmd);
}

/**
 * Function reads keyboard encoder buffer.
 * @returns	encoder buffer
 */
uint8_t kbrd_enc_read_buf() {
	return inportb(KBRD_ENC_INPUT_BUF);
}

/**
 * Send commmand to keyboard encoder.
 * Function checks if input buffer of controller is ready for input.
 * @param cmd	Command for keyboard encoder (microcontroller inside keyboard)
 */
void kbrd_enc_send_cmd(uint8_t cmd) {
	while(1)
		if ( (kbrd_ctrl_read_status() & KBRD_CTRL_STATS_MASK_IN_BUF) == 0 )
			break;
	outportb(KBRD_ENC_CMD_REG, cmd);
}

/**
 * This function is called directly by INT instruction (over IDT vector).
 * It's workaround for GCC compiler, because automatic stack framing is difficult to manage
 * and to return stack to original state. This empty function do only saves of all registers, stops interrupts and
 * call to real handler (whitch can use stack framing). Than restore registers and start intterupts again.
 */
void keyboard_irq_handler() {
	__asm__ volatile ("cli");
	__asm__ volatile ("pushal");
	keyboard_irq();
	__asm__ volatile ("popal");
	//__asm__ volatile ("movl %ebp, %esp");
	//__asm__ volatile ("popl %ebp");
	__asm__ volatile ("sti");
	__asm__ volatile ("iretl");
}

/**
 * Interrupt handler for keyboard.
 */
void __attribute__ ((cdecl)) keyboard_irq() {
	static bool _extended = false;
	int code = 0;

	// check if output buffer of controller is full
	if (kbrd_ctrl_read_status() & KBRD_CTRL_STATS_MASK_OUT_BUF) {
		// read scancode
		code = kbrd_enc_read_buf();
		// check if code is extended code, if so, than set and exit
		if (code == 0xE0 || code == 0xE1 ) {
			_extended = true;
		} else {
			_extended = false;
			// test if it is break code (test bit 7)
			if (code & 0x80) {
				code -= 0x80;
				int key = _kbrd_scancode_std [code];
				// test if a special key has been released & unset it
				switch (key) {
					case KEY_LCTRL:
					case KEY_RCTRL:
						_ctrl = false;
						break;
					case KEY_LSHIFT:
					case KEY_RSHIFT:
						_shift = false;
						break;
					case KEY_LALT:
					case KEY_RALT:
						_alt = false;
						break;
				}
			// this is start/make code
			} else {
				_scancode = code;
				int key = _kbrd_scancode_std [code];
				// test if user is holding any special keys & set it
				switch (key) {
					case KEY_LCTRL:
					case KEY_RCTRL:
						_ctrl = true;
						break;
					case KEY_LSHIFT:
					case KEY_RSHIFT:
						_shift = true;
						break;
					case KEY_LALT:
					case KEY_RALT:
						_alt = true;
						break;
					case KEY_CAPSLOCK:
						_capslock = (_capslock) ? false : true;
						kbrd_set_leds(_numlock, _capslock, _scrolllock);
						break;
					case KEY_KP_NUMLOCK:
						_numlock = (_numlock) ? false : true;
						kbrd_set_leds(_numlock, _capslock, _scrolllock);
						break;
					case KEY_SCROLLLOCK:
						_scrolllock = (_scrolllock) ? false : true;
						kbrd_set_leds(_numlock, _capslock, _scrolllock);
						break;
				}

			}

		}

		// check for errors
		switch (code) {
			case KBRD_ERR_BAT_FAILED:
				_kbrd_bat_res = false;
				break;

			case KBRD_ERR_DIAG_FAILED:
				_kbrd_diag_res = false;
				break;

			case KBRD_ERR_RESEND_CMD:
				_kbrd_resend_res = true;
				break;
		}

	}

	end_hw_interrupt(1);
}

/**
 * Return scroll lock state.
 */
bool	kbrd_get_scroll_lock() {
	return _scrolllock;
}

/**
 * Return numlock state.
 */
bool	kbrd_get_numlock() {
	return _numlock;
}

/**
 * Return capslock state.
 */
bool	kbrd_get_capslock() {
	return _capslock;
}

/**
 * Return ctrl state.
 */
bool	kbrd_get_ctrl() {
	return	_ctrl;
}

/**
 * Return alt state.
 */
bool	kbrd_get_alt() {
	return	_alt;
}

/**
 * Return shift state.
 */
bool	kbrd_get_shift() {
	return	_shift;
}

/**
 * Tells driver to ignore last resend request.
 */
void	kbrd_ignore_resend() {
	_kbrd_resend_res = false;
}

/**
 * Retur if system should redo last commands.
 */
bool	kbrd_check_resend() {
	return _kbrd_resend_res;
}

/**
 * Return diagnostic test result.
 */
bool	kbrd_get_diagnostic_res() {
	return _kbrd_diag_res;
}

/**
 * Get BAT test result.
 */
bool	kbrd_get_bat_res() {
	return _kbrd_bat_res;
}

/**
 * Get last scan code from keyboard.
 */
uint8_t	kbrd_get_last_scancode() {
	return _scancode;
}

/**
 * Set keyboards diodes.
 */
void kbrd_set_leds(bool num, bool caps, bool scroll) {
	uint8_t data = 0;

	data = (scroll==true) ? (data | 1) : (data & 1);
	data = (num==true) ? (data | 2) : (data & 1);
	data = (caps==true) ? (data | 4) : (data & 3);

	kbrd_enc_send_cmd(KBRD_ENC_CMD_SET_LED);
	kbrd_enc_send_cmd(data);
}

/**
 * Returns last pressed keycode.
 */
enum KEYCODE kbrd_get_last_key() {
	return (_scancode!=INVALID_SCANCODE) ? ((enum KEYCODE)_kbrd_scancode_std[_scancode]) : (KEY_UNKNOWN);
}

/**
 * Discart last scan
 */
void kbrd_discard_last_key() {
	_scancode = INVALID_SCANCODE;
}

/**
 * Convert key to an ASCII character
 */
char kbrd_key_to_ascii(enum KEYCODE code) {
	uint8_t key = code;

	// check if valid ascii character
	if (isascii(key)) {

		// LARGE CHARS
		if ( _shift || _capslock)
			if (key >= 'a' && key <= 'z')
				key -= 32;

		// SPECIAL CHARACTERS
		if (_shift || _capslock)
			if (key >= '0' && key <= '9')
				switch (key) {
					case '0':
						key = KEY_RIGHTPARENTHESIS;
						break;
					case '1':
						key = KEY_EXCLAMATION;
						break;
					case '2':
						key = KEY_AT;
						break;
					case '3':
						key = KEY_HASH;
						break;
					case '4':
						key = KEY_DOLLAR;
						break;
					case '5':
						key = KEY_PERCENT;
						break;
					case '6':
						key = KEY_CARRET;
						break;
					case '7':
						key = KEY_AMPERSAND;
						break;
					case '8':
						key = KEY_KP_ASTERISK;
						break;
					case '9':
						key = KEY_LEFTPARENTHESIS;
						break;
				}
			else {
				switch (key) {
					case KEY_COMMA:
						key = KEY_LESS;
						break;
					case KEY_DOT:
						key = KEY_GREATER;
						break;
					case KEY_SLASH:
						key = KEY_QUESTION;
						break;
					case KEY_SEMICOLON:
						key = KEY_COLON;
						break;
					case KEY_QUOTE:
						key = KEY_QUOTEDOUBLE;
						break;
					case KEY_LEFTBRACKET:
						key = KEY_LEFTCURL;
						break;
					case KEY_RIGHTBRACKET:
						key = KEY_RIGHTCURL;
						break;
					case KEY_GRAVE:
						key = KEY_TILDE;
						break;
					case KEY_MINUS:
						key = KEY_UNDERSCORE;
						break;
					case KEY_PLUS:
						key = KEY_EQUAL;
						break;
					case KEY_BACKSLASH:
						key = KEY_BAR;
						break;
				}
			}

		return key;
	}

	return 0;
}

void kbrd_disable() {
	kbrd_ctrl_send_cmd(KBRD_CTRL_CMD_DISABLE);
	_kbrd_disable = true;
}

void kbrd_enable() {
	kbrd_ctrl_send_cmd(KBRD_CTRL_CMD_ENABLE);
	_kbrd_disable = false;
}

bool kbrd_is_disabled() {
	return _kbrd_disable;
}

void kbrd_reset_system() {
	// write 11111110 to output port - reset system
	kbrd_ctrl_send_cmd(KBRD_CTRL_CMD_WRITE);
	kbrd_ctrl_send_cmd(0xFE);
}

void kbrd_driver_install(int irq) {
	set_handler_interrupt(irq, keyboard_irq_handler);
	_kbrd_bat_res = true;
	_scancode = 0;
	_numlock = _scrolllock = _capslock = false;
	kbrd_set_leds(false, false, false);
	_shift = _alt = _ctrl = false;
}
